﻿#region The MIT License (MIT)
// The MIT License (MIT)
// Copyright (c) 2010 - 2013 Sovacode LLC
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in all copies or substantial portions
// of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#endregion

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Xml.Serialization;
using Sovacode.RubiksCube.Common.Attributes;
using Sovacode.RubiksCube.Common.Base;
using Sovacode.RubiksCube.Common.Exceptions;

namespace Sovacode.RubiksCube.Common.Model
{
    /// <summary>
    /// Base class for data objects in model
    /// </summary>
    [Serializable]
    [DataContract]
    [DebuggerDisplay("Name = {DisplayName}")]
    [ValidateByManager]
    public class DataObject : NotifyValidatedObject, IDataObject
    {
        #region Private fields

        [DataMember(Name = "Name", EmitDefaultValue = false)]
        private string _name;

        [NonSerialized]
        [XmlIgnore]
        private bool _modified;

        [DataMember(Name = "ReadOnly", EmitDefaultValue = false)]
        private bool _readonly;

        [DataMember(Name = "Exclude", EmitDefaultValue = false)]
        private bool _removed;

        #endregion

        #region Set and notifity

        #region Expression Set and Notify

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="expression">Property</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(Expression<Func<string>> expression, ref string oldValue, string newValue)
        {
            SetPropertyAndNotify<string>(expression, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="expression">Property</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(Expression<Func<decimal>> expression, ref decimal oldValue, decimal newValue)
        {
            SetPropertyAndNotify<decimal>(expression, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="expression">Property</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(Expression<Func<double>> expression, ref double oldValue, double newValue)
        {
            SetPropertyAndNotify<double>(expression, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="expression">Property</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(Expression<Func<DateTime>> expression, ref DateTime oldValue,
                                            DateTime newValue)
        {
            SetPropertyAndNotify<DateTime>(expression, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="expression">Property</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(Expression<Func<long>> expression, ref long oldValue, long newValue)
        {
            SetPropertyAndNotify<long>(expression, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="expression"></param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify<T>(Expression<Func<T>> expression, ref T oldValue, T newValue)
        {
            SetPropertyAndNotify(expression.ExtractName(), ref oldValue, newValue);
        }

        #endregion

        #region Simple Set and Notify

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="propertyName">Property name</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(string propertyName, ref string oldValue, string newValue)
        {
            SetPropertyAndNotify<string>(propertyName, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="propertyName">Property name</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(string propertyName, ref decimal oldValue, decimal newValue)
        {
            SetPropertyAndNotify<decimal>(propertyName, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="propertyName">Property name</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(string propertyName, ref double oldValue, double newValue)
        {
            SetPropertyAndNotify<double>(propertyName, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="propertyName">Property name</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(string propertyName, ref DateTime oldValue, DateTime newValue)
        {
            SetPropertyAndNotify<DateTime>(propertyName, ref oldValue, newValue);
        }

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <param name="propertyName">Property name</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected void SetPropertyAndNotify(string propertyName, ref long oldValue, long newValue)
        {
            SetPropertyAndNotify<long>(propertyName, ref oldValue, newValue);
        }

        #endregion

        /// <summary>
        /// Set new value for property and notify
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="propertyName">Property name</param>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        protected virtual void SetPropertyAndNotify<T>(string propertyName, ref T oldValue, T newValue)
        {
            if (Readonly)
            {
                throw new ReadOnlyObjectException(this);
            }

            if (SetPropertyValue(ref oldValue, newValue))
            {
                OnPropertyChanged(propertyName);

                Modified = true;
            }

        }
        
        /// <summary>
        /// Set new value for property
        /// </summary>
        /// <typeparam name="T">Type</typeparam>
        /// <param name="oldValue">Old value</param>
        /// <param name="newValue">New value</param>
        /// <returns>Возвращает true если свойству удачно было присвоено новое значение</returns>
        private bool SetPropertyValue<T>(ref T oldValue, T newValue)
        {
            if (Equals(oldValue, newValue))
            {
                return false;
            }

            oldValue = newValue;

            return true;
        }

        #endregion

        #region Remove/Reload

        /// <summary>
        /// Remove object
        /// </summary>
        public virtual void Remove()
        {
            Removed = true;
        }

        /// <summary>
        /// Reload object
        /// </summary>
        public virtual void Reload()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Name
        /// </summary>
        [DisplayName("Name")]
        [Description("Name")]
        [Category("Basic")]
        public virtual string Name
        {
            get { return _name; }
            set
            {
                SetPropertyAndNotify(() => Name, ref _name, value);

                OnPropertyChanged(() => DisplayName);
            }
        }

        /// <summary>
        /// Display Name
        /// </summary>
        [XmlIgnore]
        [Browsable(false)]
        public virtual string DisplayName
        {
            get { return Name; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the IDataObject is modified
        /// </summary>
        [XmlIgnore]
        [Browsable(false)]
        public bool Modified
        {
            get { return _modified; }
            set
            {
                _modified = value;

                OnPropertyChanged(() => Modified);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the IDataObject is read-only
        /// </summary>
        [Browsable(false)]
        [DisplayName("Read only")]
        [Description("Read only")]
        [Category("Basic")]
        [ReadOnly(true)]
        public bool Readonly
        {
            get { return _readonly; }
            set { SetPropertyAndNotify(() => Readonly, ref _readonly, value); }
        }

        /// <summary>
        /// Gets a value indicating whether the ArrayList is removed
        /// </summary>
        [Browsable(false)]
        public bool Removed
        {
            get { return _removed; }
            private set { SetPropertyAndNotify(() => Removed, ref _removed, value); }
        }

        #endregion
    }
}